package codediff.parse;

import codediff.parse.report.CoverageReport;
import codediff.parse.report.Report;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.json.JSONObject;
import org.json.XML;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

public class XmlParseUtil {

    private static final Map<String, Class> JACOCO_XML_TYPE = new ImmutableMap.Builder<String, Class>()
            .put("package", Object.class)
            .put("report", Object.class)
            .put("class", Object.class)
            .put("sourcefile", Object.class)
            .put("line", Object.class)
            .put("method", Object.class)
            .build();

    public static Report convertXmlToJson(String xmlPath) {
        try {
            File file = new File(xmlPath);
            FileReader fileReader = new FileReader(file);
            JSONObject jsonObject = XML.toJSONObject(fileReader, true);
            Map<String, Object> stringObjectMap = convertMapToList(jsonObject.toMap());

            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false);

            return objectMapper.convertValue(stringObjectMap, Report.class);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static Map<String, Object> convertMapToList(Map<String, Object> map) {
        Map<String, Object> concurrentMap = Maps.newConcurrentMap();
        map.entrySet().forEach(entry -> {
            if (entry.getValue() instanceof Map && JACOCO_XML_TYPE.containsKey(entry.getKey())) {
                List<Map<String, Object>> mapList = convertMapChildToList(entry);
                concurrentMap.putIfAbsent(entry.getKey(), mapList);
                return;
            }
            if (entry.getValue() instanceof List) {
                List<Object> convertedMap = convertOtherTypeChildFromMapToList(entry);
                concurrentMap.putIfAbsent(entry.getKey(), convertedMap);
            }
            concurrentMap.putIfAbsent(entry.getKey(), entry.getValue());
        });
        return concurrentMap;
    }

    private static List<Map<String, Object>> convertMapChildToList(Map.Entry<String, Object> entry) {
        Map<String, Object> convertedMap = convertMapToList((Map<String, Object>) entry.getValue());
        return Collections.singletonList(convertedMap);
    }

    private static List<Object> convertOtherTypeChildFromMapToList(Map.Entry<String, Object> entry) {
        List<Object> copyOnWriteList = new CopyOnWriteArrayList<>();
        ((List) entry.getValue()).forEach(child -> {
            if (child instanceof Map) {
                copyOnWriteList.add(convertMapToList((Map<String, Object>) child));
            } else {
                copyOnWriteList.add(child);
            }
        });
        return copyOnWriteList;
    }
}
